package org.yagnus.datastructure;

/*
 * @author Hsb
 * 
 *  This class defines mathematical range on any totally ordered BASETYPE.
 *  Instances of this class are used to create non-intersecting sets or maps of ranges.
 *
 *  NOTE: equals(o) and comopareTo(o)==0 have different meaning.
 * 
 *         FIXME:The class allows only query by point or by range that is
 *         entirely contained in a specified range. Basically, need to figure
 *         out how to handle the case of closed range via joinable.
 * 
 *
 * TODO: Make available unbounded ranges.
 * 
 */
public class TotallyOrderedRange<BASETYPE extends Comparable<BASETYPE>>
        implements Comparable<TotallyOrderedRange<BASETYPE>>, Cloneable {

    final boolean openOnTheLeft, openOnTheRight;
    final BASETYPE min, max;

    protected TotallyOrderedRange(BASETYPE min, BASETYPE max,
            boolean openOnTheLeft, boolean openOnTheRight) {
        this.openOnTheLeft = openOnTheLeft;
        this.openOnTheRight = openOnTheRight;
        if (min.compareTo(max) <= 0) {
            this.min = min;
            this.max = max;
        } else {
            this.max = min;
            this.min = max;
        }
    }

    /**
     * 
     * This method tries to join the current range with the one passed in if
     * successful, return a range to take on the range represented by this range
     * and the other before call to join and return true. If join was
     * unsuccessful, then false is returned
     * 
     * In case of tie, resolve by keeping my own setting.
     * 
     * @param o
     * @return the range if join happened and null if not able to.
     */
    public TotallyOrderedRange<BASETYPE> join(TotallyOrderedRange<BASETYPE> o) {

        BASETYPE retmin = min, retmax = max;
        boolean retol = openOnTheLeft, retor = openOnTheRight;
        /* in case of joining touch open and closed sets: */
        if (min.equals(o.max) && openOnTheLeft ^ o.openOnTheRight) {
            retmin = o.min;
            retol = o.openOnTheLeft;
            return new TotallyOrderedRange<BASETYPE>(retmin, retmax, retol,
                    retor);
        }

        if (max.equals(o.min) && openOnTheRight ^ o.openOnTheLeft) {
            retmax = o.max;
            retor = o.openOnTheRight;
            return new TotallyOrderedRange<BASETYPE>(retmin, retmax, retol,
                    retor);
        }

        // Otherwise join only if we intersect
        if (this.compareTo(o) == 0) {
            if (o.min.compareTo(min) < 0) {
                retol = o.openOnTheLeft;
                retmin = o.min;

            }// otherwise keep my min and my openness

            if (o.max.compareTo(max) > 0) {
                retor = o.openOnTheRight;
                retmax = o.max;
            }

            return new TotallyOrderedRange<BASETYPE>(retmin, retmax, retol,
                    retor);
        } else {
            return null;
        }
    }

    /**
     * 
     * return an ordering on the ranges: intersection implies equality otherwise
     * return if o is left (1) or right(-1) of this object
     * 
     * @param o
     *            the other one
     */
    public final int compareTo(TotallyOrderedRange<BASETYPE> o) {

        // if the object I'm being compared to is null, by convention return
        // that I'm bigger.
        if (o == null) {
            return 1;
        }

        // test for raw equals
        if (openOnTheLeft == o.openOnTheLeft
                && openOnTheRight == o.openOnTheRight && min.equals(o.min)
                && max.equals(o.max)) {
            return 0;
        }

        // test for one
        int compare = o.max.compareTo(min);
        if (openOnTheLeft || o.openOnTheRight) {
            if (compare <= 0) {
                return 1;
            }
        } else {
            if (compare < 0) {
                return 1;
            }
        }

        // test for minus one
        compare = o.min.compareTo(max);
        if (openOnTheRight || o.openOnTheLeft) {
            if (compare >= 0) {
                return -1;
            }
        } else {
            if (compare > 0) {
                return -1;
            }
        }

        // If neither -1 nor 1 then 0
        return 0;
    }

    @Override
    /*
     * 
     * note equals is not equivalent to compareTo definition of equals
     * 
     * @param o the other object
     * 
     * @return true if this range is strictly identical to the there one.
     */
    public final boolean equals(Object o) {

        if (o == null) {
            return false;
        }

        if (o instanceof TotallyOrderedRange) {
            try {
                TotallyOrderedRange<BASETYPE> OO = (TotallyOrderedRange<BASETYPE>) o;
                return (openOnTheLeft == OO.openOnTheLeft
                        && openOnTheRight == OO.openOnTheRight
                        && min.equals(OO.min) && max.equals(OO.max));
            } catch (ClassCastException e) {
                return false;
            }
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 59 * hash + (this.openOnTheLeft ? 1 : 0);
        hash = 59 * hash + (this.openOnTheRight ? 1 : 0);
        hash = 59 * hash + (this.min != null ? this.min.hashCode() : 0);
        hash = 59 * hash + (this.max != null ? this.max.hashCode() : 0);
        return hash;
    }

    /**
     * 
     * generates a point as a closed set on one point.
     * 
     * @param p
     * @return
     */
    public static final <T_BASETYPE extends Comparable<T_BASETYPE>> TotallyOrderedRange<T_BASETYPE> getPoint(
            T_BASETYPE p) {
        return new TotallyOrderedRange<T_BASETYPE>(p, p, false, false);
    }

    /**
     * 
     * generates an open range as a closed set on one point.
     * 
     * @param p1
     * @param p2
     * @return the range, or null on error.
     */
    public static final <T_BASETYPE extends Comparable<T_BASETYPE>> TotallyOrderedRange<T_BASETYPE> getOpen(
            T_BASETYPE p1, T_BASETYPE p2) {
        if (p1.compareTo(p2) > 0) {
            return null;
        }
        return new TotallyOrderedRange<T_BASETYPE>(p1, p2, true, true);
    }

    /**
     * 
     * generates a range that is open on the left close on the right
     * 
     * @param p1
     * @param p2
     * @return the range, or null on error.
     */
    public static final <T_BASETYPE extends Comparable<T_BASETYPE>> TotallyOrderedRange<T_BASETYPE> getOpenLeft(
            T_BASETYPE p1, T_BASETYPE p2) {
        if (p1.compareTo(p2) > 0) {
            return null;
        }
        return new TotallyOrderedRange<T_BASETYPE>(p1, p2, true, false);
    }

    /**
     * 
     * generates a range that is open on the right closed on the left
     * 
     * @param p1
     * @param p2
     * @return the range, or null on error.
     */
    public static final <T_BASETYPE extends Comparable<T_BASETYPE>> TotallyOrderedRange<T_BASETYPE> getOpenRight(
            T_BASETYPE p1, T_BASETYPE p2) {
        if (p1.compareTo(p2) > 0) {
            return null;
        }
        return new TotallyOrderedRange<T_BASETYPE>(p1, p2, false, true);
    }

    /**
     * 
     * generates a range that is closed on both ends;
     * 
     * @param p1
     * @param p2
     * @return the range, or null on error.
     */
    public static final <T_BASETYPE extends Comparable<T_BASETYPE>> TotallyOrderedRange<T_BASETYPE> getClosed(
            T_BASETYPE p1, T_BASETYPE p2) {
        if (p1.compareTo(p2) > 0) {
            return null;
        }
        return new TotallyOrderedRange<T_BASETYPE>(p1, p2, false, false);
    }

    @Override
    public String toString() {
        StringBuffer ret = new StringBuffer();
        if (openOnTheLeft) {
            ret.append("(");
        } else {
            ret.append("[");
        }
        ret.append(min);
        ret.append(',');
        ret.append(max);
        if (openOnTheRight) {
            ret.append(")");
        } else {
            ret.append("]");
        }

        return ret.toString();
    }
}
